package net.cyweb.cloud.common.api.encrypt.util;

import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.symmetric.AES;
import net.cyweb.cloud.common.api.encrypt.bean.CryptoInfoBean;
import net.cyweb.cloud.common.api.encrypt.config.ApiEncryptProperties;
import net.cyweb.cloud.common.api.encrypt.core.ISecretKeyResolver;
import net.cyweb.cloud.common.api.encrypt.enums.EncryptType;
import net.cyweb.cloud.common.api.encrypt.exception.EncryptBodyFailException;
import net.cyweb.cloud.common.api.encrypt.exception.EncryptMethodNotFoundException;
import net.cyweb.cloud.common.core.util.SpringContextHolder;
import net.cyweb.cloud.common.core.util.WebUtils;
import lombok.experimental.UtilityClass;
import org.springframework.util.Assert;

import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.function.Function;

/**
 * <p>
 * 辅助检测工具类
 * </p>
 *
 * @author licoy.cn
 * @author L.cm
 * @version 2018/9/7
 */
@UtilityClass
public class ApiCryptoUtil {

    /**
     * 密钥提取器
     */
    public static final Map<EncryptType, Function<ApiEncryptProperties, String>> KEY_EXTRACTORS = Map.of(
            EncryptType.AES, ApiEncryptProperties::getAesKey,
            EncryptType.RSA, ApiEncryptProperties::getRsaPrivateKey,
            EncryptType.DES, ApiEncryptProperties::getDesKey,
            EncryptType.SM4, ApiEncryptProperties::getSm4Key
    );


    /**
     * 选择加密方式并进行加密
     *
     * @param jsonData json 数据
     * @param infoBean 加密信息
     * @return 加密结果
     */
    public static String encryptData(byte[] jsonData, CryptoInfoBean infoBean) {
        EncryptType type = infoBean.getType();
        if (type == null) {
            throw new EncryptMethodNotFoundException();
        }

        String secretKey = infoBean.getSecretKey();
        if (StrUtil.isBlank(secretKey)) {
            secretKey = SpringContextHolder.getBean(ISecretKeyResolver.class).getSecretKey(WebUtils.getRequest(), type);
        }
        Assert.hasText(secretKey, type + " key is not configured (未配置" + type + ")");

        if (type == EncryptType.DES) {

            return SecureUtil.des(secretKey.getBytes(StandardCharsets.UTF_8)).encryptBase64(jsonData);
        }
        if (type == EncryptType.AES) {
            // 构建前端对应解密AES 因子
            AES aes = new AES(Mode.CFB, Padding.NoPadding, new SecretKeySpec(secretKey.getBytes(), "AES"),
                    new IvParameterSpec(secretKey.getBytes()));

            return aes.encryptBase64(jsonData);
        }
        if (type == EncryptType.RSA) {
            return SecureUtil.rsa(secretKey.getBytes(StandardCharsets.UTF_8), null)
                    .encryptBase64(jsonData, KeyType.PrivateKey);
        }

        if (type == EncryptType.SM4) {
            return SmUtil.sm4(HexUtil.decodeHex(secretKey)).encryptHex(jsonData);
        }
        throw new EncryptBodyFailException();
    }

    /**
     * 选择加密方式并进行解密
     *
     * @param bodyData byte array
     * @param infoBean 加密信息
     * @return 解密结果
     */
    public static byte[] decryptData(byte[] bodyData, CryptoInfoBean infoBean) {
        EncryptType type = infoBean.getType();
        if (type == null) {
            throw new EncryptMethodNotFoundException();
        }

        String secretKey = infoBean.getSecretKey();
        if (StrUtil.isBlank(secretKey)) {
            secretKey = SpringContextHolder.getBean(ISecretKeyResolver.class).getSecretKey(WebUtils.getRequest(), type);
        }
        Assert.hasText(secretKey, type + " key is not configured (未配置" + type + ")");

        if (type == EncryptType.AES) {

            AES aes = new AES(Mode.CFB, Padding.NoPadding,
                    new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES"),
                    new IvParameterSpec(secretKey.getBytes(StandardCharsets.UTF_8)));
            return aes.decrypt(StrUtil.str(bodyData, StandardCharsets.UTF_8));

        }
        if (type == EncryptType.DES) {
            return SecureUtil.des(secretKey.getBytes(StandardCharsets.UTF_8)).decrypt(bodyData);
        }
        if (type == EncryptType.RSA) {
            return SecureUtil.rsa(secretKey.getBytes(StandardCharsets.UTF_8), null)
                    .decrypt(bodyData, KeyType.PrivateKey);
        }

        if (type == EncryptType.SM4) {
            return SmUtil.sm4(HexUtil.decodeHex(secretKey))
                    .decryptStr(StrUtil.str(bodyData, Charset.defaultCharset()))
                    .getBytes();
        }

        throw new EncryptMethodNotFoundException();
    }

    public static String decryptData(String bodyData, CryptoInfoBean infoBean) {
        return StrUtil.str(decryptData(bodyData.getBytes(StandardCharsets.UTF_8), infoBean), StandardCharsets.UTF_8);
    }

}
